package cn.net.xyan.easy.webapp.base.service.impl;

import cn.net.xyan.easy.spring.core.config.AutoConfigManagerFactory;
import cn.net.xyan.easy.spring.core.exception.StatusAndMessageError;
import cn.net.xyan.easy.spring.core.utils.JsonHelper;
import cn.net.xyan.easy.webapp.base.WebAppBase;
import cn.net.xyan.easy.webapp.base.dao.ConstantValueDao;
import cn.net.xyan.easy.webapp.base.dao.StatusAndTypeDao;
import cn.net.xyan.easy.webapp.base.entity.ConstantValue;
import cn.net.xyan.easy.webapp.base.entity.StatusAndType;
import cn.net.xyan.easy.webapp.base.service.ISystemConfigService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import java.lang.reflect.Constructor;
import java.util.*;

/**
 * Created by xiashenpin on 16/1/23.
 */
public class DefaultSystemConfigService implements ISystemConfigService {

    static Logger logger = LoggerFactory.getLogger(DefaultSystemConfigService.class);

    @Autowired
    ConstantValueDao valueDao;

    @Autowired
    StatusAndTypeDao statusAndTypeDao;

    Set<String> typeScanPackages;

    Map<Class<? extends StatusAndType>,String> statusTypesCache ;


    public DefaultSystemConfigService(){
        statusTypesCache = new HashMap<>();
        typeScanPackages = AutoConfigManagerFactory.autoConfigManager().readValue(WebAppBase.StatusTypeScanPackages,JsonHelper.StringSet);
        scanPackagesForStatusTypes();
    }

    @Override
    public ConstantValue setupValue(String key, Object value, String describe) {
        ConstantValue constantValue = valueDao.findByTitle(key);
        if (constantValue == null){
            constantValue = new ConstantValue(key,value);
            constantValue.setDescribe(describe);
            constantValue = valueDao.saveAndFlush(constantValue);
        }
        return constantValue;
    }

    @Override
    public ConstantValue writeValue(String key, Object value) {

        try {
            ConstantValue constantValue = valueDao.findByTitle(key);
            constantValue.setValue(JsonHelper.objectMapper().writeValueAsString(value));
            return valueDao.saveAndFlush(constantValue);
        } catch (JsonProcessingException e) {
            throw new StatusAndMessageError(-99,e);
        }

    }

    @Override
    public <T> T readValue(String key) {
        try {
            ConstantValue constantValue = valueDao.findByTitle(key);
            Class<?> type = Class.forName(constantValue.getValueClassName());
            return (T) readValue(key,type);
        } catch (ClassNotFoundException e) {
            throw new StatusAndMessageError(-99,e);
        }
    }

    @Override
    public <T> T readValue(String key, Class<T> valueType) {
        return readValue(key,JsonHelper.javaType(valueType));
    }

    @Override
    public <T> T readValue(String key, JavaType valueType) {
        try {
            ConstantValue constantValue = valueDao.findByTitle(key);
            String stringValue = constantValue.getValue();
            return JsonHelper.objectMapper().readValue(stringValue,valueType);
        } catch (Exception e) {
            throw new StatusAndMessageError(-99,e);
        }
    }

    //-----------------------------------------------------------

    private Set<Class<? extends StatusAndType>> scanTypeClasses(final String ... base) {

        Reflections reflections = new Reflections(base);

        Set<Class<? extends StatusAndType>> classes = reflections.getSubTypesOf(StatusAndType.class);
        Set<Class<? extends StatusAndType>> result = new HashSet<>();
        for (Class<?> cls : classes) {
            if (cls.getAnnotation(Entity.class) != null)
                result.add((Class<? extends StatusAndType>) cls);
        }
        return result;
    }

    protected void scanPackagesForStatusTypes(){
        if (typeScanPackages != null && typeScanPackages.size()>0 ) {
            for (Class<? extends StatusAndType> cls : scanTypeClasses(typeScanPackages.toArray(new String[0]))) {
                String type = cls.getSimpleName();
                DiscriminatorValue dv = cls.getAnnotation(DiscriminatorValue.class);
                if (dv != null) {
                    type = dv.value();
                }
                statusTypesCache.put(cls, type);
                logger.info(String.format("find state class:%s type:%s", cls.getName(), type));
            }
        }
    }

    @Override
    public <T extends StatusAndType>  T setupStatus(Class<T> type, Integer index, String title) {
        return setupStatus(type,index,title,false);
    }

    @Override
    public <T extends StatusAndType>  T  setupStatus(Class<T> type, Integer index, String title, boolean abandon) {
        String typeName = statusTypesCache.get(type);
        StatusAndType result = statusAndTypeDao.findByTypeAndIndex(typeName,index);
        if (result == null){
            Constructor<? extends StatusAndType> constructor =  null;
            try {

                constructor = type.getConstructor(Integer.class,String.class,Boolean.class);
                result = constructor.newInstance(index,title,abandon);
            } catch (NoSuchMethodException e) {
                try {
                    constructor = type.getConstructor(Integer.class,String.class);
                    result = constructor.newInstance(index,title);
                    result.setAbandon(abandon);
                } catch (NoSuchMethodException e1) {
                    try {
                        constructor = type.getConstructor();
                        result = constructor.newInstance();
                        result.setAbandon(abandon);
                        result.setIndex(index);
                        result.setTitle(title);
                    } catch (NoSuchMethodException e2) {
                        throw new StatusAndMessageError(-99,e2);
                    }catch (Exception e2) {
                        throw new StatusAndMessageError(-99,e2);
                    }
                }catch (Exception e1) {
                    throw new StatusAndMessageError(-99,e1);
                }
            } catch (Exception e) {
                throw new StatusAndMessageError(-99,e);
            }
            if (result!= null){
                result = statusAndTypeDao.saveAndFlush(result);
            }
        }
        return (T) result;
    }

    @Override
    public <T extends StatusAndType>  T  readStatus(Class<T> type, Integer index) {
        String typeName = statusTypesCache.get(type);
        return (T) statusAndTypeDao.findByTypeAndIndex(typeName,index);
    }

    @Override
    public <T extends StatusAndType>  T  readStatus(Class<T> type, String title) {
        String typeName = statusTypesCache.get(type);
        return (T) statusAndTypeDao.findByTypeAndTitle(typeName,title);
    }

    @Override
    public <T extends StatusAndType> List<T> allStatusForType(Class<T> type) {
        String typeName = statusTypesCache.get(type);
        return (List<T>) statusAndTypeDao.findByType(typeName);
    }
}
